package lumis.content.workflow;

import lumis.content.workflow.action.WorkflowSendMailActionHandler;
import lumis.portal.PortalException;
import lumis.portal.UnexpectedException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.stability.StableMinor;
import lumis.util.IResource;
import lumis.util.XmlUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.w3c.dom.*;

/**
 * Contains the definition information for a workflow.
 *
 * @composed 1 - 1..* WorkflowStateConfig
 * @composed 1 - * WorkflowRoleConfig
 * @navassoc - - * IWorkflowActionHandler
 * 
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 * @since 4.0.0
 */
@StableMinor(version = "6.0", sinceVersion = "4.0")
public class WorkflowConfig
{
	private String id;
	private String name;
	private String description;
	private String baseUrl;
	private List<String> stringsUrlPrefixes;
	private String resourceClassName;
	private IResource resource;
	private Node definition;
	private WorkflowStateConfig startState;
	private List<WorkflowStateConfig> states;
	private List<WorkflowRoleConfig> roles;
	private List<String> actionHandlerClassNames;

	/**
	 * Returns the definition for this workflow, as a string.
	 * @return the definition for this workflow, as a string.
	 * @throws PortalException
	 */
	public String getDefinition() throws PortalException
	{
		if (definition != null)
			return XmlUtil.getXmlString((Element)definition);
		else
			return null;
	}

	/**
	 * @return the definition node.
	 * @deprecated Since 4.0.11, this method is no longer used 
	 * and may be removed in the future.
	 */
	@Deprecated
	public Node getDefinitionXmlNode()
	{
		return this.definition;
	}

	/**
	 * Sets the definition for this workflow.
	 * @param definition
	 *            the definition to set.
	 */
	public void setDefinition(String definition) throws PortalException
	{
		if (definition != null)
		{
			Document workflowDoc = XmlUtil.getDocument("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + definition);
			setDefinition(workflowDoc.getDocumentElement());			
		}
		else
			this.definition = null;
	}

	/**
	 * Sets the definition for this workflow.
	 * @param workflowNode the node containing the workflow's definition.
	 * @throws PortalException
	 */
	public void setDefinition(Node workflowNode) throws PortalException
	{
		this.definition = workflowNode;
		this.id = XmlUtil.getTextContent(workflowNode.getAttributes().getNamedItem("id"));
		this.name = XmlUtil.getTextContent(workflowNode.getAttributes().getNamedItem("name"));
		this.description = XmlUtil.readNodeString("description", workflowNode);
		this.states = readStates(workflowNode);
		this.roles = readRoles(workflowNode);
		this.resourceClassName = XmlUtil.readNodeString("resource/className", workflowNode);
		this.baseUrl = XmlUtil.readNodeString("baseUrl", workflowNode);

		// load and prefix the stringsUrlPrefixes with baseUrl
		String[] arrStringsUrlPrefixes = XmlUtil.readNodesString("resource/stringsUrlPrefixes/stringsUrlPrefix", workflowNode);
		List<String> tempStringUrls = new ArrayList<String>(arrStringsUrlPrefixes.length);
		for (int i = 0; i < arrStringsUrlPrefixes.length; i++)
			tempStringUrls.add(this.baseUrl + "/" + arrStringsUrlPrefixes[i]);
		this.stringsUrlPrefixes = Collections.unmodifiableList(tempStringUrls);
		
		// set start state
		for (WorkflowStateConfig state: states)
		{
			if (state.isStart())
			{
				startState = state;
				break;
			}
		}
		if (startState == null)
			startState = states.get(0);
		
		// read action handlers
		Node actionHandlersNode = XmlUtil.selectSingleNode("actionHandlers", workflowNode);
		if (actionHandlersNode != null)
		{
			Node[] actionHandlerNodes = XmlUtil.selectNodes("actionHandler", actionHandlersNode);
			actionHandlerClassNames = new ArrayList<String>(actionHandlerNodes.length);
			for (Node actionHandlerNode: actionHandlerNodes)
			{
				String actionHandlerClassName = XmlUtil.getTextContent(actionHandlerNode);
				actionHandlerClassNames.add(actionHandlerClassName);
			}
		}
		else
		{
			// set default action handlers
			actionHandlerClassNames = Collections.singletonList(WorkflowSendMailActionHandler.class.getName());
		}
	}

	/**
	 * Reads the states from the XML definition.
	 * 
	 * @param workflowNode
	 *            the XML workflow element.
	 * @return the states read.
	 */
	private List<WorkflowStateConfig> readStates(Node workflowNode) throws PortalException
	{
		List<WorkflowStateConfig> statesList = new ArrayList<WorkflowStateConfig>();
		Node[] stateNodes = XmlUtil.selectNodes("states/state", workflowNode);
		for (Node stateNode : stateNodes)
		{
			WorkflowStateConfig stateConfig = new WorkflowStateConfig(stateNode);
			statesList.add(stateConfig);
		}
		return Collections.unmodifiableList(statesList);
	}

	/**
	 * Reads the roles from the XML definition.
	 * 
	 * @param workflowNode
	 *            the XML workflow element.
	 * @return the roles read.
	 */
	private List<WorkflowRoleConfig> readRoles(Node workflowNode) throws PortalException
	{
		List<WorkflowRoleConfig> roleList = new ArrayList<WorkflowRoleConfig>();
		Node[] stateNodes = XmlUtil.selectNodes("roles/role", workflowNode);
		for (Node roleNode : stateNodes)
		{
			WorkflowRoleConfig role = new WorkflowRoleConfig(roleNode);
			roleList.add(role);
		}
		return Collections.unmodifiableList(roleList);
	}

	/**
	 * Returns this workflow's identifier.
	 * @return this workflow's identifier.
	 */
	public String getId()
	{
		return id;
	}

	/**
	 * Sets this workflow's identifier.
	 * @param id the identifier to set.
	 */
	public void setId(String id)
	{
		this.id = id;
	}

	/**
	 * Returns the description for this workflow.
	 * @return this workflow's description, localizable using its workflow's 
	 * string resource, or <code>null</code> if not defined.
	 * @see #getResource()
	 */
	public String getDescription()
	{
		return description;
	}

	/**
	 * Sets this workflow's description.
	 * @param description the description to set.
	 * @see #getDescription()
	 */
	public void setDescription(String description)
	{
		this.description = description;
	}

	/**
	 * Returns the name for this workflow.
	 * @return this workflow's name, localizable using its workflow's 
	 * string resource.
	 * @see #getResource()
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * Sets this workflow's name.
	 * @param name the name to set.
	 * @see #getName()
	 */
	public void setName(String name)
	{
		this.name = name;
	}
	
	/**
	 * Returns the relative path to this workflow's definition directory.
	 * @return the relative path, relative to the definition directory,
	 * to this workflow's definition directory.
	 */
	public String getBaseUrl()
	{
		return baseUrl;
	}

	/**
	 * Sets the relative path to this workflow's definition directory.
	 * @param baseUrl the value to set.
	 * @see #getBaseUrl()
	 */
	public void setBaseUrl(String baseUrl)
	{
		this.baseUrl = baseUrl;
	}

	/**
	 * Returns the first string resource path for this workflow.
	 * @return the first string resource path for this workflow or 
	 * <code>null</code> if this workflow has no resource path.
	 */
	public String getStringsUrlPrefix()
	{
		if (stringsUrlPrefixes != null && !stringsUrlPrefixes.isEmpty())
			return stringsUrlPrefixes.get(0);
		else
			return null;
	}

	/**
	 * Sets the string resource path for this workflow. All current
	 * string resource paths for this workflow are discarded.
	 * @param stringsUrlPrefix the value to set.
	 */
	public void setStringsUrlPrefix(String stringsUrlPrefix)
	{
		if (stringsUrlPrefix != null)
			stringsUrlPrefixes = Collections.singletonList(stringsUrlPrefix);
		else
			stringsUrlPrefixes = null;
	}
	
	/**
	 * Returns this workflow's string resource paths.
	 * @return a list with this workflow's string resource paths, or 
	 * <code>null</code> if none was specified.
	 */
	public List<String> getStringsUrlPrefixes()
	{
		return this.stringsUrlPrefixes;
	}

	/**
	 * Returns the localization resource for this workflow.
	 * @return the localization resource for this workflow.
	 */
	public synchronized IResource getResource() throws PortalException
	{
		if (resource == null)
		{
			try
			{
				// get the class object for resource class name specified in
				// the service definition
				Class<?> resourceClass = ManagerFactory.getDeploymentManager().getClassLoader().loadClass(this.resourceClassName);

				// instantiate the resource object
				this.resource = (IResource) resourceClass.newInstance();

				// initialize resource object, adding string URL prefixes
				for (String urlPrefix: stringsUrlPrefixes)
					this.resource.addResourcePath(urlPrefix);
			}
			catch (Exception e)
			{
				throw new UnexpectedException(e);
			}
		}
		return resource;
	}

	/**
	 * Returns all states of this workflow.
	 * @return all states of this workflow.
	 */
	public List<WorkflowStateConfig> getStates()
	{
		return states;
	}

	/**
	 * Returns the specified state of this workflow.
	 * 
	 * @param stateId the state's id.
	 * @return the state.
	 * @throws WorkflowException if the state was not found.
	 */
	public WorkflowStateConfig getState(String stateId) throws WorkflowException
	{
		for (WorkflowStateConfig stateConfig : getStates())
		{
			if (stateId.equals(stateConfig.getId()))
				return stateConfig;
		}
		throw new WorkflowException("STR_WORKFLOW_STATE_NOT_FOUND;" + getId() + ";" + stateId);
	}
	
	/**
	 * Returns the start state for this workflow.
	 * @return the start state for this workflow.
	 */
	public WorkflowStateConfig getStartState()
	{
		return startState;
	}

	/**
	 * Returns the roles for this Workflow.
	 * @return the roles for this Workflow.
	 */
	public List<WorkflowRoleConfig> getRoles()
	{
		return roles;
	}

	/**
	 * Returns the action handlers for this workflow.
	 * @return the action handlers for this workflow.
	 * @since 4.0.4
	 */
	public List<IWorkflowActionHandler> getActionHandlers()
	{
		// instantiate the action handlers
		List<IWorkflowActionHandler> actionHandlers = new ArrayList<IWorkflowActionHandler>(actionHandlerClassNames.size());
		for (String className: actionHandlerClassNames)
		{
			try
			{
				Class<?> clazz = ManagerFactory.getDeploymentManager().getClassLoader().loadClass(className);
				actionHandlers.add((IWorkflowActionHandler)clazz.newInstance());
			}
			catch (Exception e)
			{
				throw new RuntimeException("Could not instantiate class '" + className + "'", e);
			}
		}
		
		return actionHandlers;
	}
}
